package com.jiawang.core.init;

import com.jiawang.core.annotations.cofig.JiaBean;
import com.jiawang.core.annotations.cofig.JiaConfiguration;
import com.jiawang.core.annotations.component.JiaComponent;
import com.jiawang.core.annotations.component.JiaController;
import com.jiawang.core.annotations.component.JiaRepository;
import com.jiawang.core.annotations.component.JiaService;
import com.jiawang.core.annotations.content.JiaRestController;
import com.jiawang.core.annotations.injection.JiaAutowired;
import com.jiawang.core.annotations.run.JiaBoot;
import com.jiawang.core.annotations.scan.JiaPackageScan;
import com.jiawang.core.constant.FileScannerConstant;
import com.jiawang.core.constant.SingleDigitConstant;
import com.jiawang.core.constant.UriConstant;
import com.jiawang.core.log.LogUtils;
import com.jiawang.core.utils.AnnotationUtils;
import com.jiawang.core.utils.JavaBeanUtil;
import org.apache.commons.lang3.StringUtils;

import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author LiJiaWang
 * 创建时间: 21:49
 * 项目的名字: jio_writer_tomcat
 * <p style="color:#33CCFF"></p>
 */
public class PackageScanner {
    private LogUtils log = new LogUtils(this.getClass().getCanonicalName());

    private Map<String, Object> classTypeMap = new HashMap<>();

    private Map<String, Object> classNameMap = new HashMap<>();
    /**
     * 开始扫描路径
     */
    private String path;
    /**
     * 扫描包名
     */
    private String packageName;
    /**
     * 基础路径
     */
    private String basePath;
    /**
     * 存放类名集合
     */
    private List<String> javaNames = new ArrayList<>();


    public List<String> getJavaNames() {
        return javaNames;
    }

    public String getPath() {
        return this.path;
    }

    @SuppressWarnings("all")
    public String getBasePath() {
        URL resource = this.getClass().getClassLoader().getResource(UriConstant.ROW);
        this.basePath = Objects.requireNonNull(resource).getPath();
        return basePath;
    }


    public String getPackageName() {
        return packageName;
    }

    public void setPackageName(String packageName) {
        this.packageName = packageName;
    }


    /**
     * 包扫描过滤
     *
     * @param clazz 启动类
     * @throws Exception 扫描失败
     */
    public void filter(Class<?> clazz) throws Exception {
        if (AnnotationUtils.recursiveJudgmentAnnotation(clazz, JiaBoot.class)) {
            log.info("包扫描初始化");
            JiaPackageScan scan = clazz.getAnnotation(JiaPackageScan.class);
            String value = scan.value();
            boolean flag = StringUtils.equals(value, UriConstant.ROW);

            if (flag) {
                this.packageName = clazz.getPackage().getName();
            } else {
                this.packageName = value;
            }
            log.info("要扫描的基础包名为: " + this.packageName);
            this.path = this.getBasePath() + this.packageName.replace(FileScannerConstant.POINT, File.separator);
            log.info("基础路径为: " + this.path);
            log.info("以" + this.path + " 为基础路径进行扫描");
            long start = System.currentTimeMillis();
            this.scanner(this.path);
            long end = System.currentTimeMillis();
            log.info("文件扫描完成,耗时" + (end - start) + "毫秒");


            register();
            injection();
        }


    }

    /**
     * 注入
     */
    private void injection() {
        Collection<Object> values = this.classTypeMap.values();
        for (Object value : values) {
            // 获取所有的成员变量
            Arrays.stream(value.getClass().getDeclaredFields()).forEach(item -> {

                String fiendName = item.getName();
                Class<? extends Field> itemClass = item.getClass();
                // 如果他是类的话

                if (item.isAnnotationPresent(JiaAutowired.class)) {
                    String canonicalName = itemClass.getCanonicalName();
                    if (itemClass.isLocalClass()) {

                        Object o = this.classTypeMap.get(canonicalName);
                        try {
                            item.set(value, o);
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                            log.server("在为" + value.getClass().getCanonicalName() + "注入的时候,没有找到这个名为" + canonicalName + "的Bean,程序结束");
                        }
                    } else { // 否则获取他的子类
                        List<String> sons = this.getSons(itemClass);
                        sons.forEach(canonical -> {
                            if (canonical.equals(canonicalName)) {
                                Object o = this.classTypeMap.get(canonicalName);
                                try {
                                    item.set(value, o);
                                } catch (IllegalAccessException e) {
                                    e.printStackTrace();
                                    log.server("在为" + value.getClass().getCanonicalName() + "注入的时候,没有找到这个名为" + canonicalName + "的Bean,程序结束");
                                }
                            }
                        });

                    }
                }

                if (item.isAnnotationPresent(Resource.class)) {
                    Object o = this.classNameMap.get(fiendName);
                    try {
                        item.set(value, o);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                        log.server("在为" + value.getClass().getCanonicalName() + "注入的时候,没有找到这个名为" + fiendName + "的Bean,程序结束");
                    }
                }
            });

        }

    }

    /**
     * 注册Bean
     *
     * @throws Exception 如果注册不成功的话,立即停止程序
     */
    private void register() throws Exception {
        for (String javaName : this.javaNames) {
            Class<?> javaClass = Class.forName(javaName);
            //判断是否带组件注解,或者配置注解
            if (AnnotationUtils.recursiveJudgmentAnnotation(javaClass, JiaComponent.class) ||
                    AnnotationUtils.recursiveJudgmentAnnotation(javaClass, JiaConfiguration.class)) {
                if (javaClass.isLocalClass()) {
                    Arrays.stream(javaClass.getAnnotations()).forEach(
                            annotation -> {
                                try {
                                    Field field = annotation.getClass().getField("value");
                                    String beanName = null;

                                    Object o = javaClass.newInstance();
                                    if (annotation instanceof JiaComponent) {
                                        beanName = (String) field.get(annotation);
                                    }
                                    if (annotation instanceof JiaController) {
                                        beanName = (String) field.get(annotation);
                                    }
                                    if (annotation instanceof JiaService) {
                                        beanName = (String) field.get(annotation);
                                    }
                                    if (annotation instanceof JiaRepository) {
                                        beanName = (String) field.get(annotation);
                                    }
                                    if (annotation instanceof JiaRestController) {
                                        beanName = (String) field.get(annotation);
                                    }
                                    if (UriConstant.ROW.equals(beanName)) {

                                        String name = javaClass.getName();
                                        String canonicalName = javaClass.getCanonicalName();
                                        classTypeMap.put(canonicalName, o);
                                        classNameMap.put(JavaBeanUtil.classNameToBeanName(name), o);
                                    }

                                    if (annotation instanceof JiaConfiguration) {

                                        Method[] methods = javaClass.getMethods();
                                        for (Method method : methods) {
                                            JiaBean jiaBean = method.getAnnotation(JiaBean.class);
                                            Optional.ofNullable(jiaBean).ifPresent(an -> {
                                                String jiaBeanName = jiaBean.value();
                                                Object result = null;
                                                try {
                                                    // 执行方法获取Bean
                                                    result = method.invoke(o);
                                                    String canonicalName = result.getClass().getCanonicalName();

                                                    classTypeMap.put(canonicalName, result);

                                                } catch (Exception e) {
                                                    e.printStackTrace();
                                                }
                                                if (UriConstant.ROW.equals(jiaBeanName)) {
                                                    jiaBeanName = method.getName();

                                                }
                                                if (result != null && !"Void".equals(result.getClass().getName())) {
                                                    classNameMap.put(jiaBeanName, result);
                                                }
                                            });
                                        }

                                    }


                                } catch (Exception e) {
                                    e.printStackTrace();
                                    log.server("注册这个Bean发生错误,它的名字为" + javaName);
                                    System.exit(1);
                                }
                            });
                }

            }
        }
        // 清除list容器
        this.javaNames.clear();
    }

    /**
     * 扫描包
     *
     * @param packagePath 包路径
     * @throws IOException 可能没有这个路径
     */
    private void scanner(String packagePath) throws IOException {
        File parentFile = new File(packagePath);
        File[] files = parentFile.listFiles();
        if (files != null) {
            for (File file : files) {
                if (file.isDirectory()) {
                    String directoryPath = file.getPath();
                    this.scanner(directoryPath);
                } else {
                    String canonicalPath = file.getCanonicalPath();
                    String javaPath = canonicalPath.substring(this.basePath.length() - SingleDigitConstant.ONE);
                    // 判断是不是java文件
                    if (javaPath.endsWith(FileScannerConstant.POINT + FileScannerConstant.CLASS)) {
                        javaPath = javaPath.substring(SingleDigitConstant.ZERO, javaPath.indexOf(FileScannerConstant.POINT));
                        String javaName = javaPath.replace(File.separator, FileScannerConstant.POINT);
                        this.javaNames.add(javaName);
                    }
                }
            }
        }
    }


    /**
     * 获取这个类的子类
     *
     * @param parentClass 父类
     * @return 子类集合
     */
    private List<String> getSons(Class<?> parentClass) {
        Set<String> strings = this.classTypeMap.keySet();
        return strings.stream().filter(item -> {
            Class<?> aClass = null;
            try {
                aClass = Class.forName(item);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
                log.server("反射失败");
            }
            return parentClass.isAssignableFrom(Objects.requireNonNull(aClass));
        }).collect(Collectors.toList());

    }


}
